#include <iostream>
using namespace std;
#include <opencv2/opencv.hpp>
using namespace cv;
// 第二题：获取当前帧前景轮廓矩形框
void foreContours(Mat& fore,Mat& foreMask,int num){
    vector<vector<Point>> cnts;
    findContours(foreMask, cnts, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    vector<Point> contour;
    double area;
    Rect rect;
    int total=0;
    for(int i=0;i<cnts.size();i++){
        contour = cnts[i];
        area = contourArea(contour);
        if(area<50){
            continue;
        }
        total++;
        rect = boundingRect(contour);
        putText(fore, to_string(total), Point(rect.x,rect.y), CV_HAL_DFT_IS_CONTINUOUS, 0.5, Scalar(0,0,0xff));
        rectangle(fore,rect,Scalar((i % 10)*20,(i % 5)*40,(i % 20)*12));
        cout<< "第" << num << "帧：第"<< total <<"个，位置（" << rect.x << "," << rect.y << "）面积：" << to_string(area) << ",长度：" << max(rect.width,rect.height) << ",宽度：" << min(rect.width,rect.height) << endl;
    }
}
// 第一题：前景提取
void getForeGround(){
    VideoCapture cap;
    Mat frame;
    Mat foreGround,backGround,foreMask,imgTemp;
    char key;
    frame = cap.open("../source/vtest.avi");
    if(!cap.isOpened()){
        printf("the video can't be opened");
    }else{
//        namedWindow("frame",WINDOW_AUTOSIZE);
//        namedWindow("foreMask",WINDOW_AUTOSIZE);
//        namedWindow("fore",WINDOW_AUTOSIZE);
//        namedWindow("back",WINDOW_AUTOSIZE);

        Ptr<BackgroundSubtractor> pBgModel = createBackgroundSubtractorMOG2().dynamicCast<BackgroundSubtractor>();
        int total=0;
        while(cap.read(frame)){
            //灰度
//            cvtColor(frame,frame,COLOR_BGR2GRAY);
            //高斯滤波
//            GaussianBlur(frame,frame,Size(5,5),1.6);
            //为了缩短计算时间，减少图像大小
            resize(frame,imgTemp,Size(frame.cols/2,frame.rows/2),INTER_LINEAR);
            //第一帧还没有背景创建一个新的前景
            if(foreGround.empty()){
                foreGround.create(imgTemp.size(),imgTemp.type());
            }
            //提取前景
            pBgModel->apply(imgTemp,foreMask);
            threshold(foreMask,foreMask,20,255,THRESH_BINARY);
            foreGround=Scalar::all(0);
            imgTemp.copyTo(foreGround,foreMask);
            //提取背景
            pBgModel->getBackgroundImage(backGround);
            total++;
            foreContours(foreGround,foreMask,total);
            imshow("frame",frame);
            imshow("foreMask",foreMask);
            imshow("fore",foreGround);
            imshow("back",backGround);
            key = waitKey(100);
            //按下esc退出
            if(key == 27){
                break;
            }
        }
    }
    cap.release();
}
// 第四题：光流估计
void opticalFlow(){
    VideoCapture cap;
    Mat img,gray,lastGray,outImg,showImg;
    vector<Point2f> points,lastPoints;
    vector<uchar> status;
    Mat err;
    char key;
    int total;
    double dist;
    img = cap.open("../source/vtest.avi");
    if(!cap.isOpened()){
        cout << "the video can't be opened" << endl;
    }else{
        while (cap.read(img)) {
            //当前帧图像灰度
            cvtColor(img, gray, COLOR_BGR2GRAY);
            //上一帧点数太少，无法进行有效判断，获取当前帧最好的前50个特征点
            if (lastPoints.size() < 10) {
                goodFeaturesToTrack(gray, lastPoints, 50, 0.01, 20);
            }
            //如果第一次，直接将上一帧灰度图为当前图
            if (lastGray.empty()) {
                gray.copyTo(lastGray);
            }
            //光流估计lk金字塔
            calcOpticalFlowPyrLK(lastGray, gray, lastPoints, points, status, err);
            //lk光流估计基于小运动假设，舍去运动幅度不够小的
            total = 0;
            for (int i = 0; i < points.size(); i++) {
                //上一帧和当前帧的运动幅度足够小，才认为是有效的特征点
                dist = norm(points[i] - lastPoints[i]);
                if (status[i] && dist >= 2 && dist <= 20) {
                    points[total] = points[i];
                    lastPoints[total++] = lastPoints[i];
                }
            }
            points.resize(total);
            lastPoints.resize(total);
            img.copyTo(showImg);
            //在原图中绘出特征点和运动连线
            for (int i = 0; i < total; i++) {
                line(showImg, lastPoints[i], points[i], Scalar(0, 0, 0xff));
                circle(showImg, points[i], 3, Scalar(0, 0xff, 0));
            }
//            imshow("input_img", img);
//            imshow("gray", gray);
            imshow("output_img", showImg);
            //交换上一帧和当前帧
            swap(gray, lastGray);
            swap(points, lastPoints);
            key = waitKey(100);
            //按esc停止
            if (key == 27) {
                break;
            }
        }
        cap.release();
    }
}
// 第三题 mac 电脑环境没法做
int main() {
    //第一题第二题
    getForeGround();
    //第四题
    opticalFlow();
    waitKey(0);
    destroyAllWindows();
    return 0;
}
